package GUI;

import Model.Gates.*;
import Model.Inputs.Button;
import Model.Inputs.Input;
import Model.Inputs.Rategen;
import Model.Main.Wire;
import Model.Main.Worksheet;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.ArrayList;
import java.util.UUID;
import java.util.concurrent.Delayed;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;

/**
 * 
 * @author rohtua
 */
public class GraphicalController extends MouseAdapter implements ActionListener, ItemListener, WindowListener {

    private static final Logger logger =
            Logger.getLogger(GraphicalController.class.getName());
    GraphicalView view;
    CompController compController;
    protected ArrayList<AbstractElementView> element = new ArrayList<AbstractElementView>();
    protected ArrayList<WireView> wires = new ArrayList<WireView>();
    protected Worksheet sheet = new Worksheet();
    private WireView wV = null;
    private Boolean wVDrawing = false;
    private Boolean clickOnNothing = true;
    private Boolean running = false;
    private Boolean iswire = false;
    private Wire wire;
    AbstractElementView fromGate;
    int fromout = 0;
    ArrayList<Point> inputs = new ArrayList<Point>();
    ArrayList<Point> outputs = new ArrayList<Point>();

    // a view meghívása és a változók alapértelmezésének beállítása
    public GraphicalController() {
        logger.finest("Creating GraphicalView");
        view = new GraphicalView(this);
    }

    public GraphicalController(Composite c) {
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        logger.info("Action from menu: " + e.getActionCommand());
        if (e.getActionCommand().equals("Exit")) {
            System.exit(0);
        }
        if (e.getSource() == view.stepButton) {
            runOnce();
        }
       
       
    }

    @Override
    public void itemStateChanged(ItemEvent e) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void windowOpened(WindowEvent e) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void windowClosing(WindowEvent e) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void windowClosed(WindowEvent e) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void windowIconified(WindowEvent e) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void windowDeiconified(WindowEvent e) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void windowActivated(WindowEvent e) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void windowDeactivated(WindowEvent e) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    /**
     * MouseListener
     * @param e 
     */
    public void mouseClicked(MouseEvent e) {
        /**
         * Koordináták kerekítése
         */
        int x = Math.round((e.getX() + 0.5f) / 10) * 10;
        int y = Math.round((e.getY() + 0.5f) / 10) * 10;
        System.out.println("Clicked on: " + e.getX() + " " + e.getY());

        /**
         * Teszteljük volt-e ott valami 
         */
        boolean reserved = false;
        Object selectedElement = null;
        for (AbstractElementView abstractElementIteration : element) {
            try {
                if (e.getX() > abstractElementIteration.position.x && e.getX() < abstractElementIteration.position.x + abstractElementIteration.getPreferredSize().getWidth() && e.getY() > abstractElementIteration.position.y && e.getY() < abstractElementIteration.position.y + abstractElementIteration.getPreferredSize().getHeight()) {
                    reserved = true;
                    view.statusTextArea.setText(abstractElementIteration.getClass().getName() + ": " + abstractElementIteration.state);
                    selectedElement = abstractElementIteration;
                    abstractElementIteration.click();


                }
            } catch (Exception err) {
                err.printStackTrace();
            }
        }

        /**
         * Ha van ott valami
         */
        if (reserved) {
            /**
             * Ha oscilloscope van, kiírjuk az értékét.
             */
            if (selectedElement.getClass().getName().contains("Oscilloscope")) {
                OscilloscopeView osc = (OscilloscopeView) selectedElement;
                ArrayList<Boolean> memory = osc.getMemory();
                view.statusTextArea.append("\nMemory of OSC: ");
                for (Boolean b : memory) {
                    int i = (b) ? 1 : 0;
                    view.statusTextArea.append("" + i + " ");
                }
                /**
                 * Ha Composite van ott ahová kattintottunk
                 * akkor megnyitjuk a sheet-jét.
                 */
            } else if (selectedElement.getClass().getName().contains("Composite")) {
                CompositeView compview = (CompositeView) selectedElement;
                if (compview.compcontroller == null) {
                    compController = new CompController(this, compview);
                    compview.addController(compController);
                } else {
                    compview.compcontroller.view.mainFrame.setVisible(true);
                }
            } else {
                delete(selectedElement);
            }
        }

        /**
         * Ha nincs ott semmi !!
         */
        if (reserved == false) {

            int a = view.list.getSelectedIndex();

            switch (a) {
                case 0:
                    andGateCreate(new Point(x, y));
                    break;
                case 1:
                    orGateCreate(new Point(x, y));
                    break;
                case 2:
                    inverterCreate(new Point(x, y));
                    break;
                case 3:
                    compositeCreate(new Point(x, y));
                    break;
                case 4:
                    ledCreate(new Point(x, y));
                    break;
                case 5:
                    oscilloscopeCreate(new Point(x, y));
                    break;
                case 6:
                    buttonCreate(new Point(x, y));
                    break;
                case 7:
                    rategenCreate(new Point(x, y));
                    break;
                case 8:
                    try {
                        wireCreate(new Point(e.getX(), e.getY()), new Point(x, y));
                    } catch (Exception err) {
                        err.printStackTrace();
                    }
                    break;
                default:
                    System.err.print("Puzzy");
            }

            try {
                String k = view.list.getSelectedValue().toString();
                System.out.println(k);
            } catch (Exception ex) {
                JOptionPane.showMessageDialog(null, "Nincs elem kijelölve");
            }
        }

        view.sheetPanel.repaint();
    }

    /**
     * Visszaadja mennyi bemenetet kér a felhasználó
     * @return 
     */
    int getInputsForGate() {
        int inputs;

        Object[] possibilities = {"2", "3", "4", "5"};

        String windowTitle = "Number of inputs";
        String windowQuestion = "Select number of inputs:";

        String str = (String) JOptionPane.showInputDialog(null, windowQuestion, windowTitle, JOptionPane.QUESTION_MESSAGE, null, possibilities, "2");

        if (str != null) {
            inputs = Integer.valueOf(str);
        } else {
            inputs = -1;
        }

        return inputs;
    }

    /**
     * Visszaadja mennyi kimenetet kér a felhasználó kompiozithoz
     * @return 
     */
    int getOutputsForComposite() {
        int inputs;

        Object[] possibilities = {"2", "3", "4", "5"};

        String windowTitle = "Number of outputs";
        String windowQuestion = "Select number of outputs:";

        String str = (String) JOptionPane.showInputDialog(null, windowQuestion, windowTitle, JOptionPane.QUESTION_MESSAGE, null, possibilities, "2");

        inputs = Integer.valueOf(str);

        return inputs;
    }

    void andGateCreate(Point point) {
        int inputs = getInputsForGate();
        if (inputs < 0 || inputs > 5) {
            return;
        }
        logger.finest("And gate is selected and preparing for insertion.");
        AndGate aG = new AndGate(inputs, UUID.randomUUID().toString());
        sheet.addGate(aG);
        AndGateView aGV = new AndGateView(aG, point);
        element.add(aGV);
        logger.finest("The gate is inserted!");
    }

    private void orGateCreate(Point point) {
        int inputs = getInputsForGate();
        if (inputs < 0 || inputs > 5) {
            return;
        }
        logger.finest("And gate is selected and preparing for insertion.");
        OrGate aG = new OrGate(inputs, UUID.randomUUID().toString());
        sheet.addGate(aG);
        OrGateView aGV = new OrGateView(aG, point);
        element.add(aGV);
        logger.finest("The gate is inserted!");
    }

    private void inverterCreate(Point point) {

        logger.finest("Inverter is selected and preparing for insertion.");
        Inverter aG = new Inverter(UUID.randomUUID().toString());
        sheet.addGate(aG);
        InverterView aGV = new InverterView(aG, point);
        element.add(aGV);
        logger.finest("The gate is inserted!");
    }

    private void compositeCreate(Point point) {
        int inputs = getInputsForGate();
        int outputs = getOutputsForComposite();
        if (inputs < 0 || inputs > 5) {
            return;
        }
        logger.finest("Composite is selected and preparing for insertion.");
        Composite composite = new Composite(inputs, outputs, UUID.randomUUID().toString());
        sheet.addGate(composite);
        CompositeView compositeV = new CompositeView(composite, point);
        element.add(compositeV);
        logger.finest("The composite is inserted!");
    }

    private void ledCreate(Point point) {
        logger.finest("Led is selected and preparing for insertion.");
        Led led = new Led(UUID.randomUUID().toString());
        sheet.addGate(led);
        LedView ledV = new LedView(led, point);
        element.add(ledV);
        logger.finest("The gate is inserted!");
    }

    private void oscilloscopeCreate(Point point) {
        logger.finest("Oscilloscope is selected and preparing for insertion.");
        Oscilloscope Oscilloscope = new Oscilloscope(UUID.randomUUID().toString());
        sheet.addGate(Oscilloscope);
        OscilloscopeView OscilloscopeV = new OscilloscopeView(Oscilloscope, point);
        element.add(OscilloscopeV);
        logger.finest("The gate is inserted!");
    }

    private void buttonCreate(Point point) {
        logger.finest("Button is selected and preparing for insertion.");
        Button button = new Button(UUID.randomUUID().toString());
        sheet.addInput(button);
        ButtonView buttonV = new ButtonView(button, point);
        element.add(buttonV);
        logger.finest("The gate is inserted!");
    }

    private void rategenCreate(Point point) {
        logger.finest("Rategen is selected and preparing for insertion.");
        Rategen rategen = new Rategen(UUID.randomUUID().toString());
        sheet.addInput(rategen);
        RategenView rategenV = new RategenView(rategen, point);
        element.add(rategenV);
        logger.finest("The gate is inserted!");
    }

    private void wireCreate(Point point, Point roundpoint) throws Exception {
        clickOnNothing = true;
        for (AbstractElementView abstractElementIteration : element) {
//            if (abstractElementIteration.outputs == null) {
//                logger.severe("HIBA@GRAPHICALCONTROLLER WIRECREATE!!!!");
//                System.exit(0);
//            }
            /**
             * outputra kattintás, nem rajzoltunk még semmit
             * megcsináljuk a wireview-et, és beállítjuk a rajzolunk flaget true-ra
             */
            if (wVDrawing == false) {
                if (abstractElementIteration.outputs != null) {
                    for (Point out : abstractElementIteration.outputs) {
                        System.out.println(point.x + " " + point.y + "  " + out.x + " " + out.y);
                        if (point.x > out.x && point.x < out.x + 6 && point.y > out.y - 3 && point.y < out.y + 4) {
                            Wire w = null;
                            if (abstractElementIteration.getClass().getName().contains("Button") || abstractElementIteration.getClass().getName().contains("Rategen")) {
                                w = abstractElementIteration.getInput().getWire();
                            } else {
                                w = abstractElementIteration.getGate().getWire(abstractElementIteration.outputs.indexOf(out));
                            }
                            if (w == null) {
                                wV = new WireView();
                                wV.addPoint(point);
                                wires.add(wV);
                                wVDrawing = true;
                                logger.info("Wire rajzolás megkezdése");
                                view.statusTextArea.setText("Kimenet kiválasztva. Vezesd a Vezetéket és irányítsd egy bemenetre");
                                fromGate = abstractElementIteration;
                                fromout = abstractElementIteration.outputs.indexOf(out);
                                return;
                            } else {
                                wV = new WireView();
                                wV.addPoint(point);
                                wires.add(wV);
                                wVDrawing = true;
                                logger.info("Wire child rajzolás megkezdése");
                                view.statusTextArea.setText("Wire kiválasztva. Vezesd a Vezetéket és irányítsd egy bemenetre");
                                iswire = true;
                                wire = w;
                                return;
                            }

                        }
                    }
                }
                /**
                 * rajzolunk flag be van kapcsolva
                 */
            } else {
                /*
                 * inputra kattintunk
                 */
                if (abstractElementIteration.inputs != null && abstractElementIteration != fromGate) {
                    for (Point in : abstractElementIteration.inputs) {


                        if (point.x > in.x - 6 && point.x < in.x + 1 && point.y > in.y - 3 && point.y < in.y + 4) {
                            if (iswire == false) {
                                clickOnNothing = false;
                                wV.addPoint(point);

                                Wire wire = new Wire(abstractElementIteration.getGate(), abstractElementIteration.inputs.indexOf(in), null);
                                if (fromGate.getClass().getName().contains("Button") || fromGate.getClass().getName().contains("Rategen")) {
                                    addWire(fromGate.getInput(), abstractElementIteration.getGate(), abstractElementIteration.inputs.indexOf(in), UUID.randomUUID().toString());
                                    wV.addWireModel(fromGate.getInput().getWire());
                                } else {
                                    addWire(fromGate.getGate(), fromout, abstractElementIteration.getGate(), abstractElementIteration.inputs.indexOf(in), UUID.randomUUID().toString());
                                    wV.addWireModel(fromGate.getGate().getWire(fromout));
                                }



                                wVDrawing = false;
                                wV = new WireView();
                                view.statusTextArea.setText("Összekötve.");
                            } else {
                                clickOnNothing = false;
                                wV.addPoint(point);

                                Wire wirech = new Wire(abstractElementIteration.getGate(), abstractElementIteration.inputs.indexOf(in), UUID.randomUUID().toString());
                                addchildWire(wirech, wire, abstractElementIteration.getGate(), abstractElementIteration.inputs.indexOf(in), UUID.randomUUID().toString());

                                wV.addWireModel(wirech);



                                wVDrawing = false;
                                wV = new WireView();
                                view.statusTextArea.setText("Összekötve.");
                                iswire = false;
                            }
                        }
                    }
                }
                if (abstractElementIteration.outputs != null) {
                    for (Point out : abstractElementIteration.outputs) {
                        if (point.x > out.x && point.x < out.x + 6 && point.y > out.y - 2 && point.y < out.y + 3) {
                            clickOnNothing = false;
                        }
                    }
                }
                if (clickOnNothing) {
                    wV.addPoint(roundpoint);
                }
            }
        }


        /**
         * Nem output, és nem inputra kattintottunk ezek szerint.
         * 
         * Ha rajzolunk, akkor felvesszünk a pointokat.
         */
    }

    private void delete(Object selectedElement) {
        if (view.list.getSelectedValue().toString().equals(" [X]Delete[X]")) {
            if (selectedElement != null && selectedElement.getClass().getSuperclass().getName().contains("Element")) {
                AbstractElementView gate = (AbstractElementView) selectedElement;
                try {
                    sheet.delGate(null);
                } catch (Exception err) {
                    logger.severe("Error: " + err.getMessage());
                }

                element.remove(selectedElement);

            }
        }
    }

    /**
     *
     * Futtatás indítása
     * 
     */
    public void start() throws InterruptedException {
        running = true;
        while (running) {
            Thread.sleep(1);
            runOnce();
        }
    }

    public void clear() {
        sheet.clear();
    }

    /**
     *
     * Futtatás leállítás, az értékek alaphelyzetbe állnak.
     */
    public void stop() {
        running = false;
        clear();

    }

    /**
     *
     *  Futtatás megállítása, minden úgy marad, ahogy a megállítás pillanatában volt.
     */
    public void pause() {
        running = false;
    }

    /*
     *
     * Egy kör szimulációt futtat.
     */
    public void runOnce() {
        logger.finest("Starting runOnce simulation!");
        int max = 0;
        for (int i = 0; i < wires.size(); i++) {
            wires.get(i).wire.setCall();
        }
        try {
            sheet.runInputsOnce();
        } catch (Exception e) {
            logger.warning("Error while sending states: " + e.getMessage());
            if (e.getMessage().equals("instabil")) {
                JOptionPane.showMessageDialog(null, "Instabil hálózat");
            }
        }
        view.sheetPanel.repaint();


    }

    /**
     * A sheet osztály setState függvényét hívja meg.
     * @param onInput : Az az Input példány referenciája, aminek az értékét szeretnénk beállítani.  
     * @param state :  A bemenet értéke
     */
    public void setState(Input onInput, Boolean state) {
        logger.log(Level.FINEST, "Call for set state state " + state + " on " + onInput + "");
        sheet.setState(onInput, state);
    }

    /**
     *A sheet osztály ugyanilyen paraméterekkel rendelkező addWire függvényét hívja meg.
     * @param from Az Input aminek a kimenetére állítjuk a wire-t
     * @param to A Gate aminek a bemenetére állítjuk a wire-t
     * @return
     */
    public void addWire(Input from, Gate to, int toInput, String name) {
        logger.log(Level.FINEST, "Call for add wire between " + from + " and " + to + " on input " + toInput + ".");
        try {
            sheet.addWire(from, to, toInput, name);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

    }

    /**
     * A sheet osztály ugyanilyen paraméterekkel rendelkező addWire függvényét hívja meg.
     * @param from  A Gate aminek a kimenetére állítjuk a wire-t
     * @param to A Gate aminek a bemenetére állítjuk a wire-t
     * @return
     */
    public void addWire(Gate from, int fromOutput, Gate to, int toInput, String name) {
        logger.log(Level.FINEST, "Call for add wire between " + from + " and " + to + " on input " + toInput + ".");
        try {
            sheet.addWire(from, fromOutput, to, toInput, name);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

    }

    /**
     *Kölyök Wire
     *
     * @param from Wire amire rá lesz kötve a wire
     * @param to a Gate amire rá akarjuk kötni
     * @param toInput A Gate bemenetének száma
     * @param name a Wire neve
     * @return
     * 
     */
    public void addchildWire(Wire child, Wire from, Gate to, int toInput, String name) throws Exception {
        sheet.addchildWire(child, from, to, toInput, name);
    }

    /**
     *A sheet osztály ugyanilyen paraméterekkel rendelkező delWire függvényét hívja meg.
     * @param from  Az Input aminek a kimenetéről töröljük a wire-t
     * @param to A Gate aminek a bemenetéről töröljük a wire-t
     * @return
     */
    public void delWire(Input from, Gate to) {
        throw new UnsupportedOperationException();
    }

    /**
     *A sheet osztály ugyanilyen paraméterekkel rendelkező delWire függvényét hívja meg.
     * @param from  A Gate aminek a kimenetéről töröljük a wire-t
     * @param to A Gate aminek a bemenetéről töröljük a wire-t
     * @return
     */
    public void delWire(Gate from, Gate to) {
        throw new UnsupportedOperationException();
    }

    /**
     * Gate hozzáadása
     * A sheet ugyanilyen paraméterekkel rendelkező addGate függvényét hívja meg.
     * @param g A hozzáadni kívánt Gate.
     * @return
     * 
     * 
     */
    public void addGate(Gate g) {
        logger.log(Level.FINEST, "Call for add " + g + " to sheet.");
        sheet.addGate(g);
    }

    /**
     * Adott Gates példány megadása a Gates Array listből.
     * @param n n. elemet adja vissza a Gates Array listből
     * @return
     *
     */
    public void getGate(int n) {
        throw new UnsupportedOperationException();
    }

    /**
     * Adott Gates példány megadása a Gates Array listből.
     * @param gate inp ezen elem értékének visszaadása a logba
     * @return
     *
     */
    public void getVal(Gate gate) {
        logger.log(Level.FINE, "" + gate.getClass().getName() + " " + gate.getName() + "'s value is: " + gate.getVal() + "");
    }

    /**
     * Inpu értékének visszaadása
     * @param inp ezen elem értékének visszaadása a logba
     * @return
     *
     */
    public void getVal(Input inp) {
        logger.log(Level.FINE, "" + inp.getClass().getName() + " " + inp.getName() + "'s value is: " + inp.getVal() + "");
    }

    /**
     * Input hozzáadása.
     * A sheet ugyanilyen paraméterekkel rendelkező addInpit függvényét hívja meg.
     * @param i Az input amit a hozzá akarunk adni.
     * @return
     * 
     */
    public void addInput(Input i) {
        logger.log(Level.FINEST, "Call for add " + i + "i to sheet.");
        sheet.addInput(i);
    }

    public void addGateComposite(Composite c, Gate g) {
        sheet.addGateComposite(c, g);

    }

    /**
     * A sheet addwire függvényét hívja meg.
     * A kompozit bemenetét köti össze egy belső kapu bemenetével.
     * @param fromInput
     * @param to
     * @param inputNumber
     */
    public void addWire(Composite comp, int fromInput, Gate to, int inputNumber, String name) throws Exception {
        sheet.addWire(comp, fromInput, to, inputNumber, name);
    }

    /**
     * A kompozit addwire függvényét hívja meg.
     * Egy belső kapu kimenetét köti össze a kompozit kimenetével.
     * @param from
     * @param outputNumber
     * @param toInput
     */
    public void addWire(Composite comp, Gate from, int outputNumber, int toOutput, String name) throws Exception {
        sheet.addWire(comp, from, outputNumber, toOutput, name);
    }

    /**
     * Gate törlése
     * @param g    
     */
    public void delGate(Gate g) throws Exception {
        sheet.delGate(g);
    }

    /**
     * Input törlése
     * 
     * @param i    
     */
    public void delInput(Input i) throws Exception {
        sheet.delInput(i);
    }

    /**
     *Inputs és Gates osztályt összekötő vezeték törlése.
     *
     * @param from az Input, amiről ki van kötve a wire
     * @param name a Wire neve
     * @return
     * 
     */
    public void delWire(Input from, String name) throws Exception {
        sheet.delWire(from, name);

    }

    /**
     *Gates és Gates osztályt összekötő vezeték törlése.
     * @param from  A Gate aminek a kimenetéről töröljük a wire-t
     * @param fromOutput a Gate melyik (int) outputjáról töröljük a wire-t
     * @param name a wire neve (ellenőrzés céljából)
     * @return
     * 
     */
    public void delWire(Gate from, int fromOutput, String name) throws Exception {
        sheet.delWire(from, fromOutput, name);
    }

    public ArrayList<AbstractElementView> getElements() {
        return element;
    }

    public ArrayList<WireView> getWires() {
        return wires;
    }
}
